package scanner

import (
	"errors"
	"learning/gooop/spring/autogen/common"
	"learning/gooop/spring/autogen/domain"
	"regexp"
	"strings"
)

type IStructScanner interface {
	ScanStruct(file *domain.CodeFileInfo)
}

type tStructScanner int

func (me *tStructScanner) ScanStruct(file *domain.CodeFileInfo) {
	bInStruct := false
	vStructs := []*domain.StructInfo{nil}
	for lineNO, line := range file.CleanLines {
		if bInStruct {
			// end?
			if gStructEndRegexp.MatchString(line) {
				me.scanMethod(vStructs[0], lineNO+1)
				file.AppendStruct(vStructs[0])

				bInStruct = false
				vStructs[0] = nil
				continue
			}

			// in struct block
			ok, fname, ftype := me.scanField(line)
			if ok {
				vStructs[0].AppendField(lineNO, fname, ftype)
			}

		} else {
			// not in struck block
			// matching start?
			if gStructStartRegexp.MatchString(line) {
				bInStruct = true
				ss := gStructStartRegexp.FindStringSubmatch(line)

				vStructs[0] = domain.NewStructInfo()
				vStructs[0].LineNO = lineNO
				vStructs[0].CodeFile = file
				vStructs[0].Name = ss[1]
				continue
			}
		}
	}
}

func (me *tStructScanner) scanField(line string) (ok bool, fldName string, fldType string) {
	if !gFieldStartRegexp.MatchString(line) {
		return false, "", ""
	}

	t := line
	s1 := gFieldStartRegexp.FindString(t)
	fldName = strings.TrimSpace(s1)

	t = t[len(s1):]
	b2, s2 := common.Tokens.MatchDataType(t)
	if !b2 {
		return false, "", ""
	}
	fldType = strings.TrimSpace(s2)

	return true, fldName, fldType
}

func (me *tStructScanner) scanMethod(stru *domain.StructInfo, fromLineNO int) {
	for i, limit := fromLineNO, len(stru.CodeFile.CleanLines); i < limit; i++ {
		line := stru.CodeFile.CleanLines[i]
		if !gMethodStartRegex.MatchString(line) {
			continue
		}

		ss := gMethodStartRegex.FindStringSubmatch(line)

		// declare
		declare := ss[0]
		offset := len(declare)

		// receiver
		receiver := ss[1]
		if receiver != stru.Name {
			continue
		}
		method := domain.NewMethodInfo()

		// name
		method.Name = ss[2]
		method.LineNO = i

		// method input args
		e, args := me.scanMethodArgs(method, strings.TrimSpace(line[offset:]))
		if e != nil {
			panic(e)
		}
		offset += len(args)

		// method return args
		e = me.scanReturnArgs(method, strings.TrimSpace(line[offset:]))
		if e != nil {
			panic(e)
		}

		// end scan method
		stru.AppendMethod(method)
	}
}

func (me *tStructScanner) scanMethodArgs(method *domain.MethodInfo, s string) (error, string) {
	t := s
	offset := 0
	for {
		// name
		b1, s1 := common.Tokens.MatchRegexp(t, `^\w+(\s*,\s*\w+)?\s+`)
		if !b1 {
			break
		}
		argNames := strings.TrimSpace(s1)
		offset += len(s1)
		t = s[offset:]

		// data type
		b2, s2 := common.Tokens.MatchDataType(t)
		if !b2 {
			return gInvalidMethodArgs, ""
		}
		argDataType := s2
		offset += len(s2)
		t = s[offset:]

		for _, it := range strings.Split(argNames, ",") {
			method.AppendArgument(it, argDataType)
		}

		// ,\s+
		b3, s3 := common.Tokens.MatchRegexp(t, `^\s*,\s*`)
		if !b3 {
			break
		}
		offset += len(s3)
		t = s[offset:]
	}

	b4, s4 := common.Tokens.MatchRegexp(t, `^\s*\)`)
	if !b4 {
		return errors.New("expecting right bracket"), ""
	}
	offset += len(s4)

	return nil, s[0:offset]
}

func (me *tStructScanner) scanReturnArgs(method *domain.MethodInfo, s string) error {
	// no args?
	if gMethodDeclarationEndRegexp.MatchString(s) {
		return nil
	}
	t := s

	// skip spaces
	b0, s0 := common.Tokens.MatchSpaces(s)
	if b0 {
		t = t[len(s0):]
	}

	// single unnamed arg?
	b0, s0 = common.Tokens.MatchDataType(t)
	if b0 {
		t = t[len(s0):]
		if gMethodDeclarationEndRegexp.MatchString(t) {
			method.AppendUnnamedReturn(s0)
			return nil
		} else {
			return errors.New("expecting ending of method declaration")
		}
	}

	// multi args start?
	b1, s1 := common.Tokens.MatchRegexp(t, `\s*\(\s*`)
	if !b1 {
		return errors.New("expecting left bracket")
	}
	t = t[len(s1):]

	// are there named args?
	b21, s21 := common.Tokens.MatchIdentifier(t)
	b22, s22 := common.Tokens.MatchSpaces(t[len(s21):])
	b23, s23 := common.Tokens.MatchDataType(t[len(s21+s22):])
	if b21 && b22 && b23 {
		// named args
		method.AppendNamedReturn(s21, s23)
		t = t[len(s21+s22+s23):]

		for {
			// more?
			b3, s3 := common.Tokens.MatchRegexp(t, `^\s*,\s*`)
			if b3 {
				// yes more
				t = t[len(s3):]
			} else {
				// no more
				break
			}

			// name
			b41, s41 := common.Tokens.MatchIdentifier(t)
			if !b41 {
				return errors.New("expecting identifier")
			}
			t = t[len(s41):]

			// \s+
			b42, s42 := common.Tokens.MatchSpaces(t)
			if !b42 {
				return errors.New("expecting spaces")
			}
			t = t[len(s42):]

			// type
			b43, s43 := common.Tokens.MatchDataType(t)
			if !b43 {
				return errors.New("expecting data type")
			}
			t = t[len(s43):]
		}

	} else {
		// unnamed args?
		for {
			b4, s4 := common.Tokens.MatchDataType(t)
			if !b4 {
				return errors.New("expecting data type")
			}
			t = t[len(s4):]
			method.AppendUnnamedReturn(s4)

			b3, s3 := common.Tokens.MatchRegexp(t, `^\s*,\s*`)
			if !b3 {
				break
			}
			t = t[len(s3):]
		}
	}

	// arguments end
	b7, _ := common.Tokens.MatchRegexp(t, `^\s*\)\s*`)
	if !b7 {
		return errors.New("expecting end of arguments")
	}

	return nil
}

var gStructStartRegexp = regexp.MustCompile(`^\s*type\s+(\w+)\s+struct\s+\{`)
var gStructEndRegexp = regexp.MustCompile(`^\s*}`)
var gFieldStartRegexp = regexp.MustCompile(`^\s*\w+\s+`)
var gMethodStartRegex = regexp.MustCompile(`^\s*func\s+\(\s*\w+\s+\*?(\w+)\s*\)\s+(\w+)\s*\(`)
var gInvalidMethodArgs = errors.New("invalid method arguments")
var gMethodDeclarationEndRegexp = regexp.MustCompile(`^\s*\{`)

var DefaultStructScanner IStructScanner = new(tStructScanner)
